package edu.pku.sei.mte.modification.command;

import edu.pku.sei.mte.exceptions.MetaModelConstraintException;
import edu.pku.sei.mte.mtemodel.model.MAttribute;
import edu.pku.sei.mte.mtemodel.model.MClass;
import edu.pku.sei.mte.mtemodel.model.MModelGraph;
import edu.pku.sei.mte.mtemodel.model.MReference;
import edu.pku.sei.mte.runtime.Context;
import edu.pku.sei.mte.runtime.rulebased.RBTEnvironment;

public abstract class ModelModificationCommand {
	private static RBTEnvironment env;
	private MModelGraph model;
	private Context context;

	public static RBTEnvironment getEnv() {
		return env;
	}

	public static void setEnv(RBTEnvironment e) {
		env = e;
	}

	public void setModel(MModelGraph model) {
		this.model = model;
	}

	public MModelGraph getModel(){
		return model;
	}
	
	public Context getContext() {
		return context;
	}

	public void setContext(Context context) {
		this.context = context;
	}

	public static void addNodeRequest(MModelGraph model, MClass node, Context context) throws Exception{
		AddingNodeCommand c = new AddingNodeCommand();
		c.setModel(model);
		c.setNewNode(node);
		c.setContext(context);
		c.execute();
	}
	
	public static void removeNodeRequest(MModelGraph model, MClass node, Context context) throws Exception{
		DeletingNodeCommand c = new DeletingNodeCommand();
		c.setModel(model);
		c.setDelNode(node);
		c.setContext(context);
		c.execute();
	}
	
	public static void addEdgeRequest(MModelGraph model, MReference edge, Context context) throws Exception{
		AddingEdgeCommand c = new AddingEdgeCommand();
		c.setModel(model);
		c.setNewEdge(edge);
		c.setContext(context);
		c.execute();
	}
	
	public static void removeEdgeRequest(MModelGraph model, MReference edge, Context context) throws Exception{
		DeletingEdgeCommand c = new DeletingEdgeCommand();
		c.setModel(model);
		c.setDelEdge(edge);
		c.setContext(context);
		c.execute();
	}
	
	public static void updateNodeRequest(MAttribute ma, Object oldValue, Object newValue, Context context) throws Exception{
		UpdatingNodeCommand c = new UpdatingNodeCommand();
		c.setAttribute(ma);
		c.setOldValue(oldValue);
		c.setNewValue(newValue);
		c.setContext(context);
		c.execute();
	}
	
	abstract public void execute() throws Exception;
	
	abstract public void undo();
	
	public boolean isViolated(){
		return false;
	}

	////////////////////////////////////////////////////////
	
	protected void commit() throws Exception{
		getEnv().getModelModificationManager().addNewCommand(this);
		if(isViolated())
			if(getEnv().getExhandler()!=null){
				MetaModelConstraintException ex = new MetaModelConstraintException();
				ex.setContext(getContext());
				getEnv().getExhandler().catchStructureException(ex);
			}
	}
	
	protected boolean basicAddNode(MClass node){
		if(!getModel().getNodes().contains(node)){
			getModel().getNodes().add(node);
			node.setHost(getModel());
			getModel().notifyNodeCreation(node);
			return true;
		}
		return false;
	}
	
	protected boolean basicRemoveNode(MClass node){
		if(getModel().getNodes().contains(node)){
			getModel().getNodes().remove(node);
			getModel().notifyNodeDeletion(node);
			return true;
		}
		return false;
	}
	
	protected boolean basicAddEdge(MReference edge){
		if(!getModel().getEdges().contains(edge)){
			getModel().getEdges().add(edge);
			if(!edge.getSource().getEdges().contains(edge))
				edge.getSource().getEdges().add(edge);
			if(!edge.getTarget().getEdges().contains(edge))
				edge.getTarget().getEdges().add(edge);
			edge.setHost(getModel());
			getModel().notifyEdgeCreation(edge);
			return true;
		}
		return false;
	}
	
	protected boolean basicRemoveEdge(MReference edge){
		if(getModel().getEdges().contains(edge)){
			getModel().getEdges().remove(edge);
			if(edge.getSource().getEdges().contains(edge))
				edge.getSource().getEdges().remove(edge);
			if(edge.getTarget().getEdges().contains(edge))
				edge.getTarget().getEdges().remove(edge);
			getModel().notifyEdgeDeletion(edge);
			return true;
		}
		return false;
	}
}
